// 
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 

using System;
using System.Collections.Generic;

using Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library.Core;
using Galaxium.Protocol.Xmpp.Library.Messaging;

using Galaxium.Protocol;
using Galaxium.Client;
using Galaxium.Core;
using Galaxium.Gui;

using Anculus.Core;

namespace Galaxium.Protocol.Xmpp
{
	public class XmppConference: AbstractConversation
	{
		private XmppConferenceContact _contact;
		private Conference _conference;
		
		private Dictionary<string, XmppPrivateConversation> _private_conversations =
			new Dictionary<string, XmppPrivateConversation> ();
		
		public event EventHandler PrivateNotify;
		
		public override bool IsChannelConversation {
			get { return true; }
		}

		public override bool IsPrivateConversation {
			get { return false; }
		}
		
		public new XmppSession Session {
			get { return base.Session as XmppSession; }
		}

		public override bool Active {
			get { return base.Active; }
			set { base.Active = value; }
		}

		public bool Connected {
			get { return _conference.Connected; }
		}
		
		private string _subject;
		
		public string Subject {
			get { return _subject; }
			private set {
				var old_val = _subject;
				_subject = value;
				var args = new EntityChangeEventArgs<string> (_contact, value, old_val);
				Session.EmitContactDisplayMessageChanged (args);
			}
		}

		public XmppConference (XmppSession session, XmppConferenceContact contact)
			:base (contact, session)
		{
			_contact = contact;
			
			_conference = new Conference (session.Client, contact.UniqueIdentifier);

			_conference.ContactAdded += HandleContactAdded;
			_conference.ContactRemoved += HandleContactRemoved;
			_conference.ContactPresenceChanged += HandleContactPresenceChanged;
			_conference.ContactAffiliationChanged += HandleContactAffiliationChanged;
			_conference.ContactRoleChanged += HandleContactRoleChanged;
			_conference.ContactNicknameChanged += HandleContactNicknameChanged;
			_conference.ContactStateChanged += HandleContactStateChanged;
			_conference.Exited += HandleExited;
			_conference.Joined += HandleJoined;
			_conference.JoinFailed += HandleJoinFailed;
			_conference.MessageReceived += HandleMessageReceived;
			_conference.SubjectChanged += HandleSubjectChanged;
			_conference.ErrorReceived += HandleErrorReceived;
			
			_conference.NicknameRequested += HandleNicknameRequested;
		}

		void HandleErrorReceived (object sender, MucErrorEventArgs e)
		{
			var message = new Message (MessageFlag.Error, _contact, Session.Account);
			message.Text = "Error: " + e.Text;
			LogMessage (message, _ready);
			OnMessageReceived (new MessageEventArgs (message, _contact));
		}

		void HandleNicknameRequested (object sender, NicknameRequestEventArgs e)
		{
			if (e.Nickname != Session.Account.DisplayName) {
				e.Nickname = Session.Account.DisplayName;
			}
			else {
				e.Nickname = e.Nickname + "_";
			}
		}
		
		public void Join ()
		{
			_conference.Join (null, null, null);
		}
		
		public void Leave ()
		{
			_conference.Leave (null);
		}
		
		public void SendMessage (string message)
		{
			_conference.SendMessage (message);
			SoundSetUtility.Play (Sound.MessageSent);
		}

		void HandleSubjectChanged (object sender, MucSubjectEventArgs e)
		{
			var real_id = String.IsNullOrEmpty (e.UID) ? "" : " (" + ((string) e.UID) + ")";
			LogEvent (e.Nickname + real_id + " changed subject to " + e.Subject);
			ThreadUtility.Dispatch (() => Subject = e.Subject);
		}

		void HandleMessageReceived (object sender, MucMessageEventArgs e)
		{
			if (e.IsPrivate) {
				var conversation = GetPrivateConversation (e.Nickname);
				conversation.HandleMessage (e.Body);
				return;
			}
			
			var source = GetEntity (e.Nickname);
			
			var flags = MessageFlag.Message | (e.IsHistory ? MessageFlag.History : 0);
			var message = new Message (flags, source, Session.Account, e.TimeStamp);
			message.SetMarkup (e.Body, null);
			LogMessage (message, _ready);
			ThreadUtility.Dispatch (() => OnMessageReceived (new MessageEventArgs (message, source)));
		}

		public XmppPrivateConversation GetPrivateConversation (string nickname)
		{
			XmppPrivateConversation conversation;
			if (!_private_conversations.TryGetValue (nickname, out conversation)) {
				var uid = new JabberID (_conference.UID.Node, _conference.UID.Domain, nickname);
				var contact = ContactCollection.GetContact (uid) as XmppMucContact;
				conversation = new XmppPrivateConversation (contact, Session);
				_private_conversations.Add (nickname, conversation);
			}
			return conversation;
		}
		
		void HandleJoinFailed (object sender, JoinFailedEventArgs e)
		{
			var text = "Entering the room wasn't successful.\n";
			switch (e.Reason) {
				case JoinFailureReason.Banned:
					text += "You are banned."; break;
				case JoinFailureReason.Cancelled:
					text += ""; break;
				case JoinFailureReason.InvalidPassword:
					text += "Password is invalid."; break;
				case JoinFailureReason.LockedRoom:
					text += "The room is locked"; break;
				case JoinFailureReason.MembersOnly:
					text += "The room is only for members."; break;
				case JoinFailureReason.NicknameConflict:
					text += "The nickname you chose is already used"
						+ " or registered by someone else."; break;
				case JoinFailureReason.NoNickname:
					text += "You didn't provide a nickname."; break;
				case JoinFailureReason.OccupantNumberLimit:
					text += "Room has reached maximum number of occupants."; break;
				case JoinFailureReason.Other:
				case JoinFailureReason.QueryFailed:
					text += e.Error.GetHumanRepresentation (); break;
			}
			var message = new Message (MessageFlag.Error, _contact, Session.Account, DateTime.Now);
			message.Text = text;
			ThreadUtility.Dispatch (() => OnMessageReceived (new MessageEventArgs (message, _contact)));
		}

		void HandleJoined (object sender, JoinEventArgs e)
		{
			LogEvent ("You entered the room " +
			          (e.IsAssignedNickname ? "with service-assigned nickname " : "as ")
			          + e.Nickname);
			if (e.RoomIsLogged)
				LogEvent ("This room has server-side logging enabled.");
			if (e.RoomIsNonAnonymous)
				LogEvent ("Everyone can see your real ID in this room.");
			if (e.RoomWasCreated)
				LogEvent ("New room has been created.\n" +
				          "You have to configure it first to make it usable.");
			
			var args = new EntityChangeEventArgs<IPresence> (_contact, _contact.Presence, null);
			ThreadUtility.Dispatch (() => Session.EmitContactPresenceChanged (args));
		}
		
		void HandleExited (object sender, MucExitEventArgs e)
		{
			string message;
			switch (e.Reason) {
				case MucExitReason.Left:
					message = "You left the room";
					break;
				case MucExitReason.AffiliationChange:
					message = "You were removed from the room because you are no longer a member";
					break;
				case MucExitReason.Banned:
					message = "You were banned from this room by " + e.Actor;
					message += "\nReason: " + e.ActorsReason;
					break;
				case MucExitReason.IsntMember:
					message = "You were removed from this room because it became"
						+ " members-only and you are not a member";
					break;
				case MucExitReason.Kicked:
					message = "You were kicked from this room by " + e.Actor;
					message += "\nReason: " + e.ActorsReason;
					break;
				case MucExitReason.SystemShutdown:
					message = "You were removed from this room because the service is going to shut down.";
					break;
				default:
					message = "You were probably somehow removed from the room," +
						" but because an error which can't occur occurred, I have no idea why.";
					break;
			}
			LogEvent (message);
			
			var args = new EntityChangeEventArgs<IPresence> (_contact, _contact.Presence, null);
			ThreadUtility.Dispatch (() => Session.EmitContactPresenceChanged (args));
			
			foreach (var contact in ContactCollection.ToArray ())
				RemoveContact (contact);
		}
		
		void HandleContactAdded (object sender, RoomContactEventArgs e)
		{
			var contact = new XmppMucContact (Session, this, e.Contact.RoomUID,
			                                  XmppPresence.Get (e.Contact.Status));
			contact.Presence = XmppPresence.Get (e.Contact.Status);
			contact.DisplayMessage = e.Contact.StatusDescription;
			ThreadUtility.Dispatch (() => AddContact (contact));
		}
		
		void HandleContactRemoved (object sender, RoomContactEventArgs e)
		{
			ThreadUtility.Dispatch (() => RemoveContact (ContactCollection.GetContact (e.Contact.RoomUID)));
		}
		

		void HandleContactStateChanged (object sender, RoomContactEventArgs e)
		{
		}

		void HandleContactNicknameChanged (object sender, RoomContactNicknameEventArgs e)
		{
			var jid = new JabberID (e.Contact.RoomUID.Node, e.Contact.RoomUID.Domain, e.OldNickname);
			var old_contact = ContactCollection.GetContact (jid) as XmppMucContact;
			var new_contact = new XmppMucContact (Session, this, e.Contact.RoomUID, old_contact.Presence);
			new_contact.DisplayMessage = old_contact.DisplayMessage;
			
			old_contact.IsBeingReplaced = true;
			new_contact.IsBeingReplaced = true;
			
			ThreadUtility.Dispatch (() => {
				RemoveContact (old_contact);
				AddContact (new_contact);
			});

			LogEvent (e.OldNickname + " has changed name to " + e.Contact.Nickname);
		}

		void HandleContactRoleChanged (object sender, RoomContactEventArgs e)
		{
			
		}

		void HandleContactAffiliationChanged (object sender, RoomContactEventArgs e)
		{
			
		}

		void HandleContactPresenceChanged (object sender, RoomContactEventArgs e)
		{
			var contact = ContactCollection.GetContact (e.Contact.RoomUID);
			ThreadUtility.Dispatch (() => {
				contact.Presence = XmppPresence.Get (e.Contact.Status);
				var args1 = new EntityChangeEventArgs<IPresence> (contact, contact.Presence, null);
				Session.EmitContactPresenceChanged (args1);
				contact.DisplayMessage = e.Contact.StatusDescription;
				var args2 = new EntityChangeEventArgs<string> (contact, contact.DisplayMessage, null);
				Session.EmitContactDisplayMessageChanged (args2);
			});
		}
		
		public void SendPrivateMessage (string nickname, string message)
		{
			_conference.SendPrivateMessage (nickname, message);
		}
		
		public override void InviteContact (IContact contact)
		{
			throw new System.NotImplementedException();
		}
		
		internal void EmitPrivateNotify ()
		{
			if (PrivateNotify != null)
				PrivateNotify (this, EventArgs.Empty);
		}
		
		public override void Close ()
		{
		}

		public IEntity GetEntity (string name)
		{
			var uid = new JabberID (_conference.UID.Node, _conference.UID.Domain, name);
			var contact = name == null ? _contact : ContactCollection.GetContact (uid);
			
			if (contact == null)
			{
				contact = new XmppMucContact (Session, this, uid, XmppPresence.Offline);
			}
			
			return contact;
		}
	}
}
